﻿using StudentAI.BitBoard;
using UvsChess;

namespace StudentAI.Algo
{
    internal static class ScoreHeuristic
    {
        #region board score constants
        private const short MATE_SCORE = 32767;
        private const short CHECK_SCORE = 75;
        //Checks in the endgame are worth more
        private const short ENDGAME_CHECK_BONUS = 40;
        //The player whose turn it is gets a slight bonus
        private const short TURN_BONUS = 10;
        //Penalty for pawns that don't have a pawn wingman (symmetrical on other half)
        private static readonly short[] ISOLATED_PAWN_PENALTY = {-12,-14,-16,-20};

        #region evaluation tables
        private static readonly short[] PAWN_TABLE = new short[]
        {    0,  0,  0,  0,  0,  0,  0,  0,
             8, 16, 24, 32, 32, 24, 16,  8,
             3, 12, 20, 28, 28, 20, 12,  3,
            -5,  4, 10, 20, 20, 10,  4, -5,
            -6,  4,  5, 16, 16,  5,  4, -6,
            -6,  4,  2,  5,  5,  2,  4, -6,
            -6,  4,  4,-15,-15,  4,  4, -6,
             0,  0,  0,  0,  0,  0,  0,  0 };

        private static readonly short[] PAWN_ENDGAME_BONUS_TABLE = new short[]
        {
         0, 0, 0, 0, 0, 0, 0, 0,
         25, 29, 29, 29, 29, 29, 29, 25,
         19, 19, 19, 19, 19, 19, 19, 19,
          9,  9,  9,  9,  9,  9,  9,  9,
          1,  1,  1,  1,  1,  1,  1,  1,
          0,  0,  0,  0,  0,  0,  0,  0,
          0,  0,  0,  0,  0,  0,  0,  0,
          0,  0,  0,  0,  0,  0,  0,  0
        };

        private static readonly short[] KNIGHT_TABLE = new short[]
        {
         -50,-40,-30,-20,-20,-30,-40,-50,
         -40,-30,  0,  0,  0,  0,-30,-40,
         -30,  5, 10, 15, 15, 10,  5,-30,
         -20,  0, 15, 20, 20, 15,  0,-20,
         -20,  0, 15, 20, 20, 15,  0,-20,
         -30,  5, 10, 15, 15, 10,  5,-30,
         -40,-30,  0,  5,  5,  0,-30,-40,
         -50,-40,-20,-20,-20,-20,-40,-50,
        };

        private static readonly short[] BISHOP_TABLE = new short[]
        {
         -20,-10,-10,-10,-10,-10,-10,-20,
         -10,  0,  0,  0,  0,  0,  0,-10,
         -10,  0,  5, 10, 10,  5,  0,-10,
         -10,  5,  5, 10, 10,  5,  5,-10,
         -10,  0, 10, 10, 10, 10,  0,-10,
         -10, 10, 10, 10, 10, 10, 10,-10,
         -10,  5,  0,  0,  0,  0,  5,-10,
         -20,-10,-40,-10,-10,-40,-10,-20,
        };
        
        private static readonly short[] KING_TABLE = new short[]
        {
          -30, -40, -40, -50, -50, -40, -40, -30,
          -30, -40, -40, -50, -50, -40, -40, -30,
          -30, -40, -40, -50, -50, -40, -40, -30,
          -30, -40, -40, -50, -50, -40, -40, -30,
          -20, -30, -30, -40, -40, -30, -30, -20,
          -10, -20, -20, -20, -20, -20, -20, -10, 
           20,  20,   0,   0,   0,   0,  20,  20,
            0,  30,  10,   0,   0,  10,  30,   0
        };
         

        private static readonly short[] KING_ENDGAME_TABLE = new short[]
        {
         -50,-40,-30,-20,-20,-30,-40,-50,
         -30,-20,-10,  0,  0,-10,-20,-30,
         -30,-10, 20, 30, 30, 20,-10,-30,
         -30,-10, 30, 40, 40, 30,-10,-30,
         -30,-10, 30, 40, 40, 30,-10,-30,
         -30,-10, 20, 30, 30, 20,-10,-30,
         -30,-20,-10,  0,  0, -10,-20,-30,
         -50,-30,-30,-30,-30,-30,-30,-50
        };
        #endregion
        #endregion

        #region piece score constants
        private const short DOUBLED_PAWN_PENALTY = -16;
        #endregion

        private static short[] whitePawnCount;
        private static short[] blackPawnCount;
        private static bool insufficientMaterial;

        private static int EvaluatePieceScore(Piece piece, byte position,
                                    bool endGamePhase, ref byte bishopCount,
                                    ref bool insufficientMaterial)
        {
            int score = 0;
            byte index = position;
            if (piece.PieceColor == ChessColor.Black)
            {
                index = (byte)(63 - position);
            }

            score += piece.PieceValue;
            score += piece.DefendedValue;
            score -= piece.AttackedValue;
            
            if (piece.DefendedValue < piece.AttackedValue)
            {
                score -= ((piece.AttackedValue - piece.DefendedValue) * 10);
            }

            if (piece.ValidMoves != null)
            {
                score += piece.ValidMoves.Count;
            }
                        
            #region piece specific
            switch(piece.PieceType){
                #region case pawn
                case ChessPieceType.Pawn :
                {
                    insufficientMaterial = false;
                    //Calculate Position Values
                    score += PAWN_TABLE[index];

                    if (endGamePhase)
                    {
                        score += PAWN_ENDGAME_BONUS_TABLE[index];
                    }

                    int col = position % 8;
                    if (piece.PieceColor == ChessColor.White)
                    {
                        if (whitePawnCount[col] > 0)
                        {
                            //Doubled Pawn
                            score += DOUBLED_PAWN_PENALTY;
                        }

                        if (position >= 8 && position <= 15)
                        {
                            if (piece.AttackedValue == 0)
                            {
                                whitePawnCount[col] += 200;

                                if (piece.DefendedValue != 0)
                                    whitePawnCount[col] += 50;
                            }
                        }
                        else if (position >= 16 && position <= 23)
                        {
                            if (piece.AttackedValue == 0)
                            {
                                whitePawnCount[col] += 100;

                                if (piece.DefendedValue != 0)
                                    whitePawnCount[col] += 25;
                            }
                        }

                        whitePawnCount[col] += 10;
                    }
                    else
                    {
                        if (blackPawnCount[col] > 0)
                        {
                            //Doubled Pawn
                            score -= DOUBLED_PAWN_PENALTY;
                        }

                        if (position >= 48 && position <= 55)
                        {
                            if (piece.AttackedValue == 0)
                            {
                                blackPawnCount[col] += 200;

                                if (piece.DefendedValue != 0)
                                    blackPawnCount[position % 8] += 50;
                            }
                        }
                        //Pawns in 6th Row that are not attacked are worth more points.
                        else if (position >= 40 && position <= 47)
                        {
                            if (piece.AttackedValue == 0)
                            {
                                blackPawnCount[col] += 100;

                                if (piece.DefendedValue != 0)
                                    blackPawnCount[position % 8] += 25;
                            }
                        }
                        blackPawnCount[col] += 10;
                    }
                }
                break;
                #endregion

                #region case knight
                case ChessPieceType.Knight :
                {
                    score += KNIGHT_TABLE[index];
                    //In the end game remove a few points for Knights since they are worth less
                    if (endGamePhase)
                    {
                        score -= 10;
                    }

                }
                break;
                #endregion

                #region case bishop
                case ChessPieceType.Bishop :
                {
                    bishopCount++;
                    if (bishopCount >= 2)
                    {
                        //2 Bishops receive a bonus
                        score += 10;
                    }

                    //In the end game Bishops are worth more
                    if (endGamePhase)
                    {
                        score += 10;
                    }

                    score += BISHOP_TABLE[index];
                }
                break;
                #endregion
                
                #region case queen
                case ChessPieceType.Queen :
                {
                    insufficientMaterial = false;
                }
                break;
                #endregion
                #region case king
                case ChessPieceType.King :
                {
                    if (piece.ValidMoves.Count < 2)
                    {
                        score -= 5;
                    }
                    
                    if (endGamePhase)
                    {
                        score += KING_ENDGAME_TABLE[index];
                    }
                    else
                    {
                        score += KING_TABLE[index];
                    }
                     
                }
        
                break;
                #endregion
                 
            }


            #endregion
            return score;
        }


        internal static void EvaluateBoardScore(Board board)
        {
            board.Score = 0;         
            if (board.StaleMate)
            {
                return;
            }

            //Next we give bonuses and penalties for board level scenarios such as King Checks, Mates
            
            if (board.BlackMate)
            {
                board.Score = MATE_SCORE;
                return;
            }
            if (board.WhiteMate)
            {
                board.Score = -MATE_SCORE;
                return;
            }
            
            if (board.BlackCheck)
            {
                board.Score += CHECK_SCORE;
                if (board.EndGamePhase)
                    board.Score += ENDGAME_CHECK_BONUS;
            }
            else if (board.WhiteCheck)
            {
                board.Score -= CHECK_SCORE;
                if (board.EndGamePhase)
                    board.Score -= ENDGAME_CHECK_BONUS;
            }
   
            //Add a small bonus for turn
            if (board.WhoseMove == ChessColor.White)
            {
                board.Score += TURN_BONUS;
            }
            else
            {
                board.Score -= TURN_BONUS;
            }
            /*The following two integers will keep track of how many bishops and knights are remaining on the board.  
             * This will allow us to give an additional bonus if a player has both bishops.  Also if there 2 are 2 knights 
             * we can't call the Insufficient Material Tie rule.
             */

            byte blackBishopCount = 0;
            byte whiteBishopCount = 0;
            byte knightCount = 0;
            /*The following integer will calculate remaining material on the board.  
             * We will use this later on to make the decision if we are currently in the middle or end game.  
             * End game evaluation can differ from the middle game.  For example in the middle game Knights are very useful 
             * since they can hop behind enemy lines and take out vulnerable pieces, however in an end game Knights have a 
             * hard time placing the king in a corner for a mate so their value is slightly diminished.  
             */

            int remainingPieces = 0;
            /*
             * We also need to keep track how many pawns exist in each column so that later we can figure out if we have any isolated and doubled pawns.
             */

            blackPawnCount = new short[8];
            whitePawnCount = new short[8];
            int blackCount=0;
            int whiteCount = 0; ;
            //The next step of the evaluation is a loop through all of the chess pieces on the chess board and call the EvaluatePieceScore method defined above.
            for (byte x = 0; x < 64; x++)
            {
                Piece piece = board.Squares[x];
                if (piece == null)
                    continue;

                //Calculate Remaining Material for end game determination
                remainingPieces++;

                if (piece.PieceColor == ChessColor.White)
                {
                    blackCount++;
                    board.Score += EvaluatePieceScore(piece, x, board.EndGamePhase,
                     ref whiteBishopCount, ref insufficientMaterial);
                }
                else if (piece.PieceColor == ChessColor.Black)
                {
                    whiteCount++;
                    board.Score -= EvaluatePieceScore(piece, x, board.EndGamePhase,
                     ref blackBishopCount, ref insufficientMaterial);
                }

                if (piece.PieceType == ChessPieceType.Knight)
                {
                    knightCount++;

                    if (knightCount > 1)
                    {
                        insufficientMaterial = false;
                    }
                }

                if ((blackBishopCount + whiteBishopCount) > 1)
                {
                    insufficientMaterial = false;
                }
            }

            /*
            * Next section does will handle the remaining board level events, such as calling a tie if there is insufficient material on the chess board.
            * After looping through all of the chess pieces we also know how many pieces are remaining on the chess board.  If there are less than 10 pieces
            * we will mark the chess board as being in an endgame.  This way the next evaluation will change slightly based on the end game rules defined.
            */

            if (insufficientMaterial)
            {
                board.Score = 0;
                board.StaleMate = true;
                board.InsufficientMaterial = true;
                return;
            }

            //Mark as endgame
            if (remainingPieces < 10 || whiteCount<5 || blackCount<5)
            {
                board.EndGamePhase = true;
            }

            /*
             * The last section of code uses the previously stored pawn position information and figures out if there are any doubled and isolated pawns.  
             * Each one of these pawns are given a strong penalty.
             */
            //Black Isolated Pawns            
            if (blackPawnCount[0] >= 1 && blackPawnCount[1] == 0)
            {
                board.Score -= ISOLATED_PAWN_PENALTY[0];
            }
            if (blackPawnCount[1] >= 1 && blackPawnCount[0] == 0 && blackPawnCount[2] == 0)
            {
                board.Score -= ISOLATED_PAWN_PENALTY[1];
            }
            if (blackPawnCount[2] >= 1 && blackPawnCount[1] == 0 && blackPawnCount[3] == 0)
            {
                board.Score -= ISOLATED_PAWN_PENALTY[2];
            }
            if (blackPawnCount[3] >= 1 && blackPawnCount[2] == 0 && blackPawnCount[4] == 0)
            {
                board.Score -= ISOLATED_PAWN_PENALTY[3];
            }
            if (blackPawnCount[4] >= 1 && blackPawnCount[3] == 0 && blackPawnCount[5] == 0)
            {
                board.Score -= ISOLATED_PAWN_PENALTY[3];
            }
            if (blackPawnCount[5] >= 1 && blackPawnCount[2] == 0 &&
             blackPawnCount[6] == 0)
            {
                board.Score -= ISOLATED_PAWN_PENALTY[2];
            }
            if (blackPawnCount[6] >= 1 && blackPawnCount[5] == 0 &&
             blackPawnCount[7] == 0)
            {
                board.Score -= ISOLATED_PAWN_PENALTY[1];
            }
            if (blackPawnCount[7] >= 1 && blackPawnCount[6] == 0)
            {
                board.Score -= ISOLATED_PAWN_PENALTY[0];
            }
            //White Isolated Pawns
            if (whitePawnCount[0] >= 1 && whitePawnCount[1] == 0)
            {
                board.Score += ISOLATED_PAWN_PENALTY[0];
            }
            if (whitePawnCount[1] >= 1 && whitePawnCount[0] == 0 &&
             whitePawnCount[2] == 0)
            {
                board.Score += ISOLATED_PAWN_PENALTY[1];
            }
            if (whitePawnCount[2] >= 1 && whitePawnCount[1] == 0 &&
             whitePawnCount[3] == 0)
            {
                board.Score += ISOLATED_PAWN_PENALTY[2];
            }
            if (whitePawnCount[3] >= 1 && whitePawnCount[2] == 0 &&
             whitePawnCount[4] == 0)
            {
                board.Score += ISOLATED_PAWN_PENALTY[3];
            }
            if (whitePawnCount[4] >= 1 && whitePawnCount[3] == 0 &&
             whitePawnCount[5] == 0)
            {
                board.Score += ISOLATED_PAWN_PENALTY[3];
            }
            if (whitePawnCount[5] >= 1 && whitePawnCount[4] == 0 &&
             whitePawnCount[6] == 0)
            {
                board.Score += ISOLATED_PAWN_PENALTY[2];
            }
            if (whitePawnCount[6] >= 1 && whitePawnCount[5] == 0 &&
             whitePawnCount[7] == 0)
            {
                board.Score += ISOLATED_PAWN_PENALTY[1];
            }
            if (whitePawnCount[7] >= 1 && whitePawnCount[6] == 0)
            {
                board.Score += ISOLATED_PAWN_PENALTY[0];
            }

            //Black Passed Pawns
            if (blackPawnCount[0] >= 1 && whitePawnCount[0] == 0)
            {
                board.Score -= blackPawnCount[0];
            }
            if (blackPawnCount[1] >= 1 && whitePawnCount[1] == 0)
            {
                board.Score -= blackPawnCount[1];
            }
            if (blackPawnCount[2] >= 1 && whitePawnCount[2] == 0)
            {
                board.Score -= blackPawnCount[2];
            }
            if (blackPawnCount[3] >= 1 && whitePawnCount[3] == 0)
            {
                board.Score -= blackPawnCount[3];
            }
            if (blackPawnCount[4] >= 1 && whitePawnCount[4] == 0)
            {
                board.Score -= blackPawnCount[4];
            }
            if (blackPawnCount[5] >= 1 && whitePawnCount[5] == 0)
            {
                board.Score -= blackPawnCount[5];
            }
            if (blackPawnCount[6] >= 1 && whitePawnCount[6] == 0)
            {
                board.Score -= blackPawnCount[6];
            }
            if (blackPawnCount[7] >= 1 && whitePawnCount[7] == 0)
            {
                board.Score -= blackPawnCount[7];
            }
            
            //White Passed Pawns
            if (whitePawnCount[0] >= 1 && blackPawnCount[1] == 0)
            {
                board.Score += whitePawnCount[0];
            }
            if (whitePawnCount[1] >= 1 && blackPawnCount[1] == 0)
            {
                board.Score += whitePawnCount[1];
            }
            if (whitePawnCount[2] >= 1 && blackPawnCount[2] == 0)
            {
                board.Score += whitePawnCount[2];
            }
            if (whitePawnCount[3] >= 1 && blackPawnCount[3] == 0)
            {
                board.Score += whitePawnCount[3];
            }
            if (whitePawnCount[4] >= 1 && blackPawnCount[4] == 0)
            {
                board.Score += whitePawnCount[4];
            }
            if (whitePawnCount[5] >= 1 && blackPawnCount[5] == 0)
            {
                board.Score += whitePawnCount[5];
            }
            if (whitePawnCount[6] >= 1 && blackPawnCount[6] == 0)
            {
                board.Score += whitePawnCount[6];
            }
            if (whitePawnCount[7] >= 1 && blackPawnCount[7] == 0)
            {
                board.Score += whitePawnCount[7];
            }             
        }
    }
}
